package gbench.sandbox.neo4j.graph;

import org.junit.jupiter.api.Test;

import java.io.File;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import gbench.common.fs.FileSystem;
import gbench.common.matlib.data.DataReader.DFrame;
import gbench.common.tree.LittleTree;
import gbench.common.tree.LittleTree.IRecord;
import gbench.common.tree.LittleTree.Jdbc;
import gbench.common.tree.LittleTree.Json;
import gbench.common.tree.LittleTree.Jdbc.JdbcConfig;
import gbench.common.tree.LittleTree.Jdbc.JdbcExecute;
import gbench.common.tree.LittleTree.Jdbc.JdbcQuery;
import gbench.common.tree.LittleTree.Jdbc.SqlPatternPreprocessor;
import gbench.commonApp.jdbc.IJdbcApp;
import gbench.commonApp.jdbc.ISqlDatabase;
import gbench.commonApp.jdbc.Neo4jApp;

import static gbench.common.tree.LittleTree.*;
import static gbench.common.matlib.MatlibCanvas.*;
import static gbench.common.tree.LittleTree.IRecord.*;
import static gbench.common.tree.LittleTree.KVPair.*;

/**
 * 通用数据库的查询演示 <br>
 * 多级路径的定义图结构
 * 
 * @author gbench
 *
 */
public class JunitX {
    /**
     * neo4j 数据库操作接口<br>
     * Neo4jDatabase
     * 
     * @author gbench
     *
     */
    @JdbcConfig(url = "jdbc:neo4j:bolt://localhost/mydb3?nossl", driver = "org.neo4j.jdbc.Driver", user = "neo4j", password = "123456")
    interface Neo4jDatabase extends ISqlDatabase { // 数据接口
        @JdbcQuery
        List<IRecord> actIn(String name);

        @JdbcQuery
        List<IRecord> summary();

        @JdbcExecute
        void purchase(long transcode, String customer, String cashier, String product, String shop, double quantity,
                String unit, double price, Date createtime);
    }

    /**
     * 创建 Neo4jApp 对象
     * 
     * @return Neo4jApp
     */
    public Neo4jApp neo4jApp() {
        return Neo4jApp.newInstance("", null, null).set_intialize_handler(g -> {// 把交易代码也写入到边明路径中。
            final var edg_renderer = g.getEdge_name_renderer();// 记录老的边名记录器
            g.setEdge_name_renderer(edge -> edg_renderer.apply(edge) + "_" + edge.attr("code", 0));// 在边实体记录中增加transcode
            if (true)
                g.setAttr_ops((attributeSets, rec) -> {
                    final var key = rec.str("key"); // 边名/顶点名
                    final var name = rec.str("name"); // 属性名
                    final var value = rec.get("value", e -> (e instanceof IRecord)
                            ? ((IRecord) e).json().replace("\"", "\\\"")
                            : e != null && !e.getClass().isPrimitive() && !Number.class.isAssignableFrom(e.getClass()) && !(e instanceof String)
                                    ? LittleTree.trycatch(obj -> Json.obj2json(obj).replace("\"", "\\\"")).apply(e)
                                    : e);// 属性值
                    final var as = attributeSets.compute(key, (partB, v) -> (v == null ? REC() : v));
                    as.compute(name, v -> {
                        if (v == null) {
                            return value;
                        } else {
                            final var ll = as.pathL(name);
                            if (!ll.contains(value))
                                ll.add(value);
                            return ll.size() > 1 ? ll : ll.get(0);
                        }
                    });
                });
        }, false);// 注释intialize_handler 采用增量覆盖的方式 即replace 参数使用false
    }

    /**
     * neo4j 的使用演示
     * 注意 路径格式为: [START][A,B,C][O,P,Q][X,Y,Z][END] 的样式,不能采用 "/" 进行分隔 <br>
     * 对于a/b/c的路径请使用[a][b][c]来代替
     */
    @Test
    public void foo() {
        System.setProperty("user.timezone", "Asia/Shanghai"); // 设置时间格式
        final var database = IJdbcApp.newNspebDBInstance("graph.sql", Neo4jDatabase.class); // 创建数据库像
        final var proxy = database.getProxy(); // 提取代理对象
        final var jdbc = proxy.findOne(Jdbc.class); // 获取jdbc对象
        final var filepath = FileSystem.path("x.txt", this.getClass());
        final var ai = new AtomicInteger(0);// 边的序号
        final var pattern = Pattern.compile("\\s*([^\\{]+)(\\{[^\\}]+\\})\\s*"); // name {key:value} 的模式
        final var eis = FileSystem.utf8lines(new File(filepath)).flatMap(line -> {
            final var splits = line.split("-+>"); // 分解路径模式
            final var path = splits[0].strip();// 第一部分路径信息
            final var names = (splits.length > 1 ? splits[1] : "REL").strip(); // 第二部分为属性信息
            return Arrays.stream(names.split("\\|")) // 分隔多边
                    .flatMap(e -> {
                        final var matcher = pattern.matcher(e);
                        var name = MFT("`{0}`", e);// 边的名称
                        var param = REC();// 边的数据参数
                        if (matcher.matches()) {// 边属性定义的模式
                            name = MFT("`{0}`", matcher.group(1)); // 获取边的名称
                            final var jsn = matcher.group(2).replaceAll("([\\w_][^\\\"]+):", "\\\"$1\\\":");// 对于:前面的字符字符串自动添加引号
                            param.add(Json.json2rec(jsn));
                        }
                        final var attr = REC("0#vlblk", "PartA", "1#vlblk", "PartB", "elblk", name,"code",ai.getAndIncrement(),"name", name).add(param); // 边的属性数据
                        final var pp = Arrays.stream(path.split("[\\]]+")) // 路径使用[] 进行分隔,路径的示例是诸如[a][b,c][d,e,f]这样的形式
                                .map(o->o.strip()).map(p->p.startsWith("[") && !p.endsWith("]")?(p+"]"):p)
                                .collect(Collectors.toList()); // 二元路径分解,pp[0] 前驱, pp[1] 后继
                        return IRecord.slidingS(pp, 2, 1, true).map(ss ->{ // 生成二元路径&属性
                                final var kvp = KVP(MFT(ss[0].endsWith("]") || ss[1].startsWith("[") ? "{0}{1}" : "{0}/{1}", ss[0], ss[1]), attr); // (二元路径,路径属性) 的键值对儿
                                println("二元路径&属性",kvp);
                                return kvp;// 返回 二元路径&属性
                        });// ss map 
                    });// names flatMap
        }).collect(Neo4jApp.eisclc("[,\\s]+")); // eis utf8lines flatMap
        final var g = neo4jApp().graph(eis);
        println("图结构:\n", g);

        jdbc.withTransaction(sess -> {
            sess.sqlexecute("match (a) detach delete a");
            sess.sqlexecute(MFT("create {0}", g));
            println(sess.sql2u("match p=(a)-->(b) return p", DFrame.dfmclc));
            println(sess.sql2u("match (a)-[*1]->(b) where a.name='##name' return distinct b.name",REC("name","START"), DFrame.dfmclc));
        }, Jdbc.M(SqlPatternPreprocessor.class, proxy.findOne(SqlPatternPreprocessor.class)));
    }
}
